%{
#include "ast.h"
#include "errmsg.h"
#include "symbol.h"
#include "utils.h"
#include "semantic.h"
//#include "codegen.h"

int yylex(void);
void yyerror(char *msg);
void parse(string_t filename, bool codegen);
extern void codegen_prog(ast_decl_t prog);
%}

%union {
    int pos;
		int ival;
		float fval;
		string_t str;
		char chr;
		bool bval;
		list_t list;
		symbol_t sym;
		ast_decl_t decl;
		ast_stmt_t stmt;
		ast_expr_t expr;
		ast_type_t type;
}

%{
#define LIST_ACTION(target, prev, elem) \
    do \
    { \
        list_t p, e = list((elem), NULL); \
        (target) = p = (prev); \
        if (p) \
        { \
            while (p->next) \
                p = p->next; \
            p->next = e; \
        } \
        else \
            (target) = e; \
    } \
    while (false)

static ast_decl_t _program;
%}

%debug

%token <pos>
		CLASS LBRACE RBRACE SEMICOLON
		LPARAN RPARAN RETURN COMMA
		LBRACK RBRACK IF ELSE WHILE ASSIGN
		NOT DOT THIS NEW INT CHAR FLOAT
		STRING BOOLEAN AND OR LT GT LE GE
		EQ NEQ PLUS MINUS TIMES DIVIDE COLON
		VOID CALL LENGTH
%token <ival> INT_LIT  
%token <fval> FLOAT_LIT 
%token <str> IDF STRING_LIT
%token <chr> CHAR_LIT
%token <bval> BOOLEAN_LIT

%left SEMICOLON COMMA
%right ASSIGN
%left OR
%left AND
%left EQ NEQ
%left LT GT LE GE
%left PLUS MINUS
%left TIMES DIVIDE
%right NOT NEW
%left DOT LBRACK RBRACK


%type <decl> program class_decl var_decl method_decl
%type <stmt> stmt 
%type <expr> expr
%type <type> type primitive_type class_type array_type
%type <sym>  idf
%type <list> var_decls method_decls para_decls stmts args para_decl arg


%start program

%%

program:
		class_decl
		{ _program = $1; }
		;

class_decl:
		CLASS class_type LBRACE var_decls method_decls RBRACE
		{ $$ = ast_class_decl($1, $2, $4, $5); }
		;

var_decls:
		{ $$ = NULL; }
|		var_decls var_decl
		{ LIST_ACTION($$, $1, $2); }
		;

var_decl:
    COLON type idf SEMICOLON
    { $$ = ast_var_decl($1, $2, $3); }
    ;

method_decls:
		{ $$ = NULL; }
|		method_decls method_decl
		{ LIST_ACTION($$, $1, $2); }
		;
    
method_decl:
    type idf LPARAN para_decls RPARAN LBRACE var_decls stmts RETURN expr SEMICOLON RBRACE
    { $$ = ast_method_decl($3, $1, $2, $4, $7, $8, $10); }
|   VOID idf LPARAN para_decls RPARAN LBRACE var_decls stmts RBRACE
    { $$ = ast_method_decl($1, NULL, $2, $4, $7, $8, NULL); }
    ;

para_decls:
		{ $$ = NULL; }
|		type idf para_decl
    { $$ = list(ast_para_decl($1->pos, $1, $2), $3); }
		//{ LIST_ACTION($$, $3, ast_para_decl($1->pos, $1, $2)); }
		;

para_decl:
		{ $$ = NULL; }
|   para_decl COMMA type idf
		{ LIST_ACTION($$, $1, ast_para_decl($2, $3, $4)); }
		;

type:
		primitive_type
		{ $$ = $1; }
|		class_type
    { $$ = $1; }
|	  array_type
    { $$ = $1; }
		;
		

primitive_type:
    INT { $$ = ast_primitive_type($1, AST_INT); }
|   FLOAT { $$ = ast_primitive_type($1, AST_FLOAT); }
|   CHAR { $$ = ast_primitive_type($1, AST_CHAR); }
|   STRING { $$ = ast_primitive_type($1, AST_STRING); }
|   BOOLEAN { $$ = ast_primitive_type($1, AST_BOOLEAN); }
    ;

class_type:
		idf
		{ $$ = ast_class_type(em_tok_pos, $1); }
		;
    
array_type:
    INT LBRACK RBRACK
    { $$ = ast_array_type($1, AST_INT); }
|   FLOAT LBRACK RBRACK
    { $$ = ast_array_type($1, AST_FLOAT); }
|   CHAR LBRACK RBRACK
    { $$ = ast_array_type($1, AST_CHAR); }
|   STRING LBRACK RBRACK
    { $$ = ast_array_type($1, AST_STRING); }
|   BOOLEAN LBRACK RBRACK
    { $$ = ast_array_type($1, AST_BOOLEAN); }
    ;
  
stmts:
		{ $$ = NULL; }
|   stmts stmt
		{ LIST_ACTION($$, $1, $2); }
		;
    
stmt:
    LBRACE stmts RBRACE
    { $$ = ast_stmts_stmt($1, $2); }
|		IF LPARAN expr RPARAN stmt ELSE stmt
		{ $$ = ast_if_stmt($1, $3, $5, $7); }
|   WHILE LPARAN expr RPARAN stmt
		{ $$ = ast_while_stmt($1, $3, $5); }
|		idf ASSIGN expr SEMICOLON
		{ $$ = ast_var_assign_stmt($2, $1, $3); }
|		idf LBRACK expr RBRACK ASSIGN expr SEMICOLON
		{ $$ = ast_arr_assign_stmt($2, $1, $3, $6); }
|		CALL expr DOT idf LPARAN args RPARAN SEMICOLON 
		{ $$ = ast_call_stmt($1, $2, $4, $6); }
		;

expr:
		expr AND expr
		{ $$ = ast_op_expr($2, $1, AST_AND, $3); }
|   expr OR expr
		{ $$ = ast_op_expr($2, $1, AST_OR, $3); }
|   expr LT expr
		{ $$ = ast_op_expr($2, $1, AST_LT, $3); }
|   expr GT expr
		{ $$ = ast_op_expr($2, $1, AST_GT, $3); }
|		expr LE expr
		{ $$ = ast_op_expr($2, $1, AST_LE, $3); }
|		expr GE expr
		{ $$ = ast_op_expr($2, $1, AST_GE, $3); }
|		expr EQ expr
		{ $$ = ast_op_expr($2, $1, AST_EQ, $3); }
|		expr NEQ expr
		{ $$ = ast_op_expr($2, $1, AST_NEQ, $3); }
|		expr PLUS expr
		{ $$ = ast_op_expr($2, $1, AST_PLUS, $3); }
|   expr MINUS expr
		{ $$ = ast_op_expr($2, $1, AST_MINUS, $3); }
|   expr TIMES expr
		{ $$ = ast_op_expr($2, $1, AST_TIMES, $3); }
|		expr DIVIDE expr
		{ $$ = ast_op_expr($2, $1, AST_DIVIDE, $3); }
|   NOT expr
		{ $$ = ast_not_expr($1, $2); }
|		expr LBRACK expr RBRACK
		{ $$ = ast_arr_access_expr($2, $1, $3); }
|		expr DOT idf LPARAN args RPARAN
		{ $$ = ast_method_access_expr($2, $1, $3, $5); }
|   expr DOT LENGTH
		{ $$ = ast_arr_length_expr($2, $1); }
|   idf
		{ $$ = ast_idf_expr(em_tok_pos, $1);}
|   THIS
		{ $$ = ast_this_expr($1); }
|		NEW idf LPARAN RPARAN
		{ $$ = ast_class_init_expr($1, $2); }
|		NEW primitive_type LBRACK expr RBRACK
		{ $$ = ast_arr_init_expr($1, $2, $4); }
|		LBRACE args RBRACE
		{ $$ = ast_arr_decl_expr($1, $2); }
|   LPARAN expr RPARAN
		{ $$ = $2;  }
|		INT_LIT
		{ $$ = ast_int_expr(em_tok_pos, $1); }
|		FLOAT_LIT
		{ $$ = ast_float_expr(em_tok_pos, $1); }
| 	CHAR_LIT
		{ $$ = ast_char_expr(em_tok_pos, $1); }
|		STRING_LIT
		{ $$ = ast_string_expr(em_tok_pos, $1); }
|		BOOLEAN_LIT
		{ $$ = ast_bool_expr(em_tok_pos, $1); }
		;

args:
		{ $$ = NULL; }
|		expr arg
    { $$ = list($1, $2); }
		//{ LIST_ACTION($$, $2, $1); }
		;

arg:
		{ $$ = NULL; }
| 	arg COMMA expr 
		{ LIST_ACTION($$, $1, $3); }
		;

idf:
	 IDF 
	 { $$ = symbol($1); }
	 ;

%%

void yyerror(char *msg)
{
    em_error(em_tok_pos, "%s", msg);
}

void parse(string_t filename, bool codegen)
{
		em_reset(filename);
		if (!em_any_errors && yyparse() == 0) {
				print_ast_decl(_program, 0);
				sem_trans_prog(_program);
				if (codegen)
				  codegen_prog(_program);
		}
}
